package com.kexio.file.storage.impl;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.LinkOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import com.kexio.file.config.FileStorageProperties;
import com.kexio.file.storage.StorageFileInfo;
import com.kexio.file.storage.StorageProvider;
import com.kexio.file.storage.StorageResult;
import com.kexio.file.util.FileHashUtils;

/**
 * 本地文件存储提供者
 * 
 * 实现基于本地文件系统的存储功能
 * 
 * @author Kexio Team
 * @since 1.0.0
 */
@Component("localStorageProvider")
public class LocalStorageProvider implements StorageProvider {
    
    private static final Logger log = LoggerFactory.getLogger(LocalStorageProvider.class);
    
    private static final String STORAGE_TYPE = "LOCAL";
    
    @Autowired
    private FileStorageProperties storageProperties;
    
    @Override
    public String getStorageType() {
        return STORAGE_TYPE;
    }
    
    @Override
    public StorageResult storeFile(String bucketName, String objectKey, InputStream inputStream, 
                                   long contentLength, String contentType, Map<String, String> metadata) {
        try {
            // 构建完整的文件路径
            Path filePath = buildFilePath(bucketName, objectKey);
            
            // 确保目录存在
            Files.createDirectories(filePath.getParent());
            
            // 写入前后校验根边界与符号链接
            try {
                Path root = getRootRealPath();
                Path parentNormalized = filePath.getParent().normalize();
                ensureUnderRoot(root, parentNormalized);
                // 写入文件
                Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING);
                Path fileNormalized = filePath.normalize();
                ensureUnderRoot(root, fileNormalized);
            } catch (IOException se) {
                return StorageResult.failure("路径安全检查失败: " + se.getMessage());
            }
            
            // 验证文件大小
            long actualSize = Files.size(filePath);
            if (contentLength > 0 && actualSize != contentLength) {
                // 尺寸不匹配则回滚并失败
                try { Files.deleteIfExists(filePath); } catch (IOException ignore) {}
                String msg = String.format("文件大小不匹配: 期望=%d, 实际=%d, 文件=%s", contentLength, actualSize, filePath);
                log.warn(msg);
                return StorageResult.failure(msg);
            }
            
            // 构建存储结果
            StorageResult result = StorageResult.success(bucketName, objectKey);
            result.setFileSize(actualSize);
            result.setContentType(contentType);
            result.setStorageUrl(filePath.toUri().toString());
            result.setMetadata(metadata);
            
            log.info("本地存储成功: bucket={}, key={}, size={}, path={}", 
                    bucketName, objectKey, actualSize, filePath);
            
            return result;
            
        } catch (IOException e) {
            log.error("本地存储失败: bucket={}, key={}", bucketName, objectKey, e);
            return StorageResult.failure("本地存储失败: " + e.getMessage());
        }
    }
    
    @Override
    public InputStream getFile(String bucketName, String objectKey) {
        try {
            Path filePath = buildFilePath(bucketName, objectKey);
            try {
                Path root = getRootRealPath();
                // 使用 normalize() 而不是 toRealPath() 来避免Windows上的路径问题
                Path normalized = filePath.normalize();
                ensureUnderRoot(root, normalized);
            } catch (IOException se) {
                log.warn("路径安全检查失败: {}, path: {}", se.getMessage(), filePath);
                return null;
            }
            
            if (!Files.exists(filePath)) {
                log.warn("文件不存在: bucket={}, key={}, path={}", bucketName, objectKey, filePath);
                return null;
            }
            
            return new FileInputStream(filePath.toFile());
            
        } catch (FileNotFoundException e) {
            log.error("获取本地文件失败: bucket={}, key={}", bucketName, objectKey, e);
            return null;
        }
    }
    
    @Override
    public boolean deleteFile(String bucketName, String objectKey) {
        try {
            Path filePath = buildFilePath(bucketName, objectKey);
            try {
                Path root = getRootRealPath();
                Path normalized = filePath.normalize();
                ensureUnderRoot(root, normalized);
            } catch (IOException se) {
                log.warn("路径安全检查失败: {}", se.getMessage());
                return false;
            }
            
            if (!Files.exists(filePath)) {
                log.warn("要删除的文件不存在: bucket={}, key={}, path={}", bucketName, objectKey, filePath);
                return true; // 文件不存在也算删除成功
            }
            
            Files.delete(filePath);
            
            // 尝试删除空的父目录
            cleanupEmptyDirectories(filePath.getParent());
            
            log.info("本地文件删除成功: bucket={}, key={}, path={}", bucketName, objectKey, filePath);
            return true;
            
        } catch (IOException e) {
            log.error("删除本地文件失败: bucket={}, key={}", bucketName, objectKey, e);
            return false;
        }
    }
    
    @Override
    public boolean fileExists(String bucketName, String objectKey) {
        Path filePath = buildFilePath(bucketName, objectKey);
        return Files.exists(filePath) && Files.isRegularFile(filePath);
    }
    
    @Override
    public StorageFileInfo getFileInfo(String bucketName, String objectKey) {
        Path filePath = buildFilePath(bucketName, objectKey);
        
        if (!Files.exists(filePath)) {
            return new StorageFileInfo(false);
        }
        
        try {
            StorageFileInfo fileInfo = new StorageFileInfo(true);
            fileInfo.setFileSize(Files.size(filePath));
            fileInfo.setLastModified(LocalDateTime.ofInstant(
                Files.getLastModifiedTime(filePath).toInstant(), 
                java.time.ZoneId.systemDefault()));
            
            // 尝试探测内容类型
            String contentType = Files.probeContentType(filePath);
            fileInfo.setContentType(contentType);
            
            return fileInfo;
            
        } catch (IOException e) {
            log.error("获取文件信息失败: bucket={}, key={}", bucketName, objectKey, e);
            return new StorageFileInfo(false);
        }
    }
    
    @Override
    public String generatePreSignedUploadUrl(String bucketName, String objectKey, 
                                            int expireMinutes, String contentType) {
        // 本地存储不支持预签名URL
        log.warn("本地存储不支持预签名上传URL");
        return null;
    }
    
    @Override
    public String generatePreSignedDownloadUrl(String bucketName, String objectKey, int expireMinutes) {
        // 本地存储不暴露文件系统路径，统一走受控下载接口
        log.warn("本地存储不支持预签名下载URL");
        return null;
    }
    
    @Override
    public boolean copyFile(String sourceBucket, String sourceKey, String destBucket, String destKey) {
        try {
            Path sourcePath = buildFilePath(sourceBucket, sourceKey);
            Path destPath = buildFilePath(destBucket, destKey);
            try {
                Path root = getRootRealPath();
                Path sourceNormalized = sourcePath.normalize();
                ensureUnderRoot(root, sourceNormalized);
                Path destParentNormalized = destPath.getParent().normalize();
                ensureUnderRoot(root, destParentNormalized);
            } catch (IOException se) {
                log.warn("路径安全检查失败: {}", se.getMessage());
                return false;
            }
            
            if (!Files.exists(sourcePath)) {
                log.warn("源文件不存在: bucket={}, key={}", sourceBucket, sourceKey);
                return false;
            }
            
            // 确保目标目录存在
            Files.createDirectories(destPath.getParent());
            
            // 复制文件
            Files.copy(sourcePath, destPath, StandardCopyOption.REPLACE_EXISTING);
            
            log.info("本地文件复制成功: {}:{} -> {}:{}", 
                    sourceBucket, sourceKey, destBucket, destKey);
            return true;
            
        } catch (IOException e) {
            log.error("本地文件复制失败: {}:{} -> {}:{}", 
                    sourceBucket, sourceKey, destBucket, destKey, e);
            return false;
        }
    }
    
    @Override
    public boolean createBucketIfNotExists(String bucketName) {
        try {
            Path bucketPath = buildBucketPath(bucketName);
            
            if (!Files.exists(bucketPath)) {
                Files.createDirectories(bucketPath);
                log.info("创建本地存储目录: {}", bucketPath);
            }
            
            return true;
            
        } catch (IOException e) {
            log.error("创建本地存储目录失败: bucket={}", bucketName, e);
            return false;
        }
    }
    
    @Override
    public boolean verifyFileIntegrity(String bucketName, String objectKey, String expectedHash) {
        try {
            Path filePath = buildFilePath(bucketName, objectKey);
            
            if (!Files.exists(filePath)) {
                return false;
            }
            
            try (InputStream inputStream = Files.newInputStream(filePath)) {
                String actualHash = FileHashUtils.calculateSha256(inputStream);
                return expectedHash.equalsIgnoreCase(actualHash);
            }
            
        } catch (IOException e) {
            log.error("验证文件完整性失败: bucket={}, key={}", bucketName, objectKey, e);
            return false;
        }
    }
    
    // ==================== 私有方法 ====================
    
    /**
     * 构建文件路径
     */
    private Path buildFilePath(String bucketName, String objectKey) {
        Path bucketPath = buildBucketPath(bucketName);
        String key = objectKey == null ? "" : objectKey;
        // 规范化，防止目录穿越
        Path relative = Paths.get(key).normalize();
        if (relative.isAbsolute() || relative.startsWith("..")) {
            throw new IllegalArgumentException("非法的对象键");
        }
        // 如果启用了日期子目录
        if (storageProperties.getLocal().isCreateDateSubDir()) {
            String dateDir = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
            return bucketPath.resolve(dateDir).resolve(relative).normalize();
        } else {
            return bucketPath.resolve(relative).normalize();
        }
    }
    
    /**
     * 构建存储桶路径
     */
    private Path buildBucketPath(String bucketName) {
        String rootPath = storageProperties.getLocal().getRootPath();
        return Paths.get(rootPath).resolve(StringUtils.hasText(bucketName) ? bucketName : "default");
    }

    private Path getRootRealPath() throws IOException {
        return Paths.get(storageProperties.getLocal().getRootPath()).normalize().toAbsolutePath();
    }

    private void ensureUnderRoot(Path root, Path child) throws IOException {
        Path normalizedRoot = root.normalize().toAbsolutePath();
        Path normalizedChild = child.normalize().toAbsolutePath();
        
        log.debug("路径安全检查: root={}, child={}", normalizedRoot, normalizedChild);
        
        if (!normalizedChild.startsWith(normalizedRoot)) {
            throw new IOException("越界访问被拒绝: " + normalizedChild + " 不在 " + normalizedRoot + " 下");
        }
    }
    
    /**
     * 清理空目录
     */
    private void cleanupEmptyDirectories(Path directory) {
        try {
            // 只清理空的日期目录，不清理根目录和bucket目录
            if (directory != null && Files.exists(directory) && isEmptyDirectory(directory)) {
                Path parent = directory.getParent();
                Files.delete(directory);
                
                // 递归清理父目录（但不超过bucket级别）
                if (parent != null && !parent.equals(buildBucketPath(""))) {
                    cleanupEmptyDirectories(parent);
                }
            }
        } catch (IOException e) {
            // 清理失败不影响主流程
            log.debug("清理空目录失败: {}", directory, e);
        }
    }
    
    /**
     * 检查目录是否为空
     */
    private boolean isEmptyDirectory(Path directory) throws IOException {
        try (var stream = Files.list(directory)) {
            return !stream.findFirst().isPresent();
        }
    }
}
